/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package StoreServerSide;

import LoggerServerSide.Clock;

/**
 * Esta classe StoreMonitor cria um objecto do tipo Loja onde vão interagir a dona e os clientes.
 * Neste monitor numa versão prévia foi incluído um array de estados do cliente, que depois podia ter sido retirada
 * mas optou-se por continuar com este array para nos podermos guiar ao longo do programa.
 *
 * @author Pedro Costa Correia - 32795
 * @author Valter Pedro Lopes Vicente - 39360
 */
public class StoreMonitor {

    /**
     *  Este tipo de dados implementa a Loja.
     *  Solução concorrente baseada em monitores como elemento de sincronização entre os threads Customer e Owner.
     */
    /**
     *  Constantes que caracterizam o estado interno dos threads cliente
     */
    public enum customerState {

        /**
         *  estado independente na simulação, o cliente deve ser posto a dormir durante um intervalo de tempo aleatório
         */
        CARRYING_OUT_DAILY_CHORES,
        /**
         * estado de transição, verificação se a porta da loja está aberta
         */
        CHECKING_DOOR_OPEN,
        /**
         * estado de transição, verificação das prateleiras da loja por produtos
         */
        APPRAISING_OFFER_IN_DISPLAY,
        /**
         * estado de bloqueio o cliente é acordado pela dona da empresa quando completa a transacção, sayGoodByeToCustomer
         */
        BUYING_SOME_GOODS
    }
    /**
     * Parâmetro booleano que define o estado da loja.
     * false - Fechada
     * true - Aberta
     */
    private boolean storeState = true;
    /**
     *  Vector de armazenamento da quantidade de produtos comprados pelos clientes.
     *
     *    @serialField customerProducts
     */
    private int customerProducts[];
    /**
     *  Número de clientes que frequentam a loja
     *
     *    @serialField nCustomer
     */
    private int nCustomer = 0;
    /**
     * Número de clientes dentro da loja
     *
     * @serialField nCustInStore
     */
    private int nCustInStore = 0;
    /**
     *  Estado presente dos clientes
     *
     *    @serialField customerState
     */
    private customerState[] customersState;
    /**
     * Número de peças na loja
     *
     * @serialField nProducts
     */
    private int nProducts = 0;
    /**
     *  Parâmetro booleano que define se a dona está morta ou não.
     *
     * @serialField donaIsDead
     */
    private boolean donaIsDead = false;
    /**
     *  Registo dos clientes a dormir
     *
     *    @serialField registry
     */
    private Thread[] registry;

    /**
     *  Instanciação da loja.
     *
     *    @param nCustomer Número de clientes que frequentam a loja.
     *    @param nProducts Número de produtos no ínicio da loja.
     *    @param ownerState Estado geral onde se encontra o fifo de pedidos e o estado da dona da loja.
     *    @param map Mapa onde vão ser guardados os registos de log.
     */
    public StoreMonitor(int nCustomer, int nProducts) {

        this.nProducts = nProducts;
        this.nCustomer = nCustomer;

        customerProducts = new int[nCustomer];
        customersState = new customerState[this.nCustomer];

        for (int i = 0; i < this.nCustomer; i++) {
            customersState[i] = customerState.CARRYING_OUT_DAILY_CHORES;
            customerProducts[i] = 0;
        }

    }

    /**
     * Método de finalização da thread cliente.
     * @param customerId.
     * @return <li> true - quando o cliente pode morrer, ou seja, quando a dona está morta.
     *		<li> false - caso o cliente não tenha de morrer.
     *
     */
    public synchronized boolean endOperCustomer(int customerId) {
        if (isDonaIsDead()) /**Informação que a dona não vai recarregar mais peças*/
        {
            return (true);
        } else {
            if (nCustInStore == 0 && nProducts == 0 && ownerState.isAllCraftsDead() && !isDonaIsDead()) {
                ownerState.emptyRequest();
            }
        }
        return (false);
    }

    /**
     *Método de finalização da thread dona da loja
     * @return<li> true - quando a dona está em fase que pode morrer, ou seja quando os artesãos estao todos mortos,
     *			não existem clientes na loja  e que não existem mais produtos para os mesmos comprarem.
     *		<li> false - caso contrário.
     */
    public synchronized boolean endOperEntrep() {
        if (ownerState.isAllCraftsDead() && nCustInStore == 0 && nProducts == 0) {
            donaIsDead = true;

            return (true);
        } else {
            return (false);
        }
    }

    /**
     * Monitor invocado pela dona da loja indicando que a loja foi fechada com sucesso.
     *
     */
    public synchronized void closeTheDoor() {
        mlog = new Message();
        this.storeState = false;
        mlog.setShopDoorStat(Message.storeStat.SDCL);
        map.put(Clock.getInstance().getValue(), mlog);
    }

    /**
     * Monitor invocado pela dona definindo a abertura da loja.
     *
     */
    public synchronized void prepareToWork() {
         if (this.nProducts != 0) {
            this.storeState = true;
            mlog.setOwnerStat(Message.ownerStat.WFNT);
            mlog.setShopDoorStat(Message.storeStat.SPOP);
        } else {
            mlog.setOwnerStat(Message.ownerStat.WFNT);
        }
        map.put(Clock.getInstance().getValue(), mlog);
    }

    /**
     * Monitor invocado pela dona, alterando o estado do cliente e da dona, depois desta o ter atendido.
     *
     * @param customerId Identificação do cliente.
     */
    public synchronized void sayGoodByeToCustomer(int customerId) {
        mlog = new Message();
        customersState[customerId] = customerState.CARRYING_OUT_DAILY_CHORES;	// sinaliza que o cliente vai prosseguir com a sua vida
        notifyAll();
        mlog.setOwnerStat(Message.ownerStat.ATAC);
        mlog.setstatCustomer(Message.customerStat.CODC, customerProducts[customerId], customerId);
        if (this.nProducts == 0) {
            closeTheDoor();
        }
        map.put(Clock.getInstance().getValue(), mlog);
    }

    /**
     * Monitor invocado pela dona para verificação da existencia de clientes na loja
     * @return <li> true, se existirem.
     *            <li> false, em caso contrário.
     */
    public synchronized boolean customersInTheShop() {
        if (nCustInStore != 0) {
            return (true);
        } else {
            return (false);
        }
    }

    /**
     *   Monitor invocado pela dona, sinalizando que fechou a loja e vai em direcção à oficina.
     */
    public void prepareToLeave() {
        mlog = new Message();
        mlog.setOwnerStat(Message.ownerStat.CLTS);
        mlog.setShopStat(nCustInStore, nProducts, '0', '0');
        map.put(Clock.getInstance().getValue(), mlog);
    }

    /**
     * Monitor invocado pela dona, informando que regressou à loja.
     */
    public synchronized void returnToShop() {
        mlog = new Message();
        int i;
        i = ownerState.getNProductsTransport();

        if (i != 0) {
            nProducts = nProducts + i;
            ownerState.setNProductsTransport(0);
        }
        mlog.setOwnerStat(Message.ownerStat.OPTS);
        mlog.setShopStat(nCustInStore, nProducts, '0', '0');

        map.put(Clock.getInstance().getValue(), mlog);
    }

    /**
     *  O cliente verifica os produtos expostos na loja e escolhe um (operação interna).
     *
     * @param customerId Identificação do cliente.
     * @return Quantidade de produtos escolhidos pelo cliente.
     */
    public synchronized int perusingAround(int customerId) {
        mlog = new Message();
        int result = randint(0, nProducts);
        customerProducts[customerId] = customerProducts[customerId] + result;
        this.nProducts = nProducts - result;
        mlog.setShopStat(nCustInStore, nProducts, '0', '0');

        map.put(Clock.getInstance().getValue(), mlog);
        return result;
    }

    /**
     * Monitor invocado pelo cliente, com a pretensão de ir visitar a loja.
     *
     * @param customerId Identificação do cliente.
     */
    public synchronized void goShopping(int customerId) {
        customersState[customerId] = customerState.CHECKING_DOOR_OPEN;
    }

    /**
     * Monitor invocado pelo cliente, entrando este na loja se esta estiver aberta ou voltando às suas tarefas caso contrário.
     * @param customerId Identificação do cliente.
     */
    public synchronized void enterShop(int customerId) {
        mlog = new Message();
        if (isDoorOpen()) {
            nCustInStore += 1;
            customersState[customerId] = customerState.APPRAISING_OFFER_IN_DISPLAY;
            mlog.setstatCustomer(Message.customerStat.AOID, customerProducts[customerId], customerId);
            mlog.setShopStat(nCustInStore, nProducts, '0', '0');

        } else {
            customersState[customerId] = customerState.CARRYING_OUT_DAILY_CHORES;
            mlog.setstatCustomer(Message.customerStat.CODC, customerProducts[customerId], customerId);
            mlog.setShopStat(nCustInStore, nProducts, '0', '0');
        }
        map.put(Clock.getInstance().getValue(), mlog);
    }

    /**
     * Monitor invocado pelo cliente, qunado este sai da loja e volta à vida normal.
     *
     * @param customerId Identificação do cliente.
     */
    public synchronized void exitShop(int customerId) {
        mlog = new Message();
        nCustInStore -= 1;
        mlog.setstatCustomer(Message.customerStat.CODC, customerProducts[customerId], customerId);
        mlog.setShopStat(nCustInStore, nProducts, '0', '0');
        map.put(Clock.getInstance().getValue(), mlog);
    }

    /**
     *  Operação de ir comprar algum produto (originada pelo cliente).
     *
     * @param customerId Identificação do cliente.
     * @param ng Número de produtos que o cliente pretende.
     *
     * @return <li> true, se conseguiu comprar o que desejava.
     *            <li> false, em caso contrário.
     */
    public synchronized boolean iWantThis(int customerId, int ng) {
        mlog = new Message();
        customersState[customerId] = customerState.BUYING_SOME_GOODS;

        // aguarda a continuação das operações
        while (customersState[customerId] != customerState.CARRYING_OUT_DAILY_CHORES) {
            mlog.setstatCustomer(Message.customerStat.BYSG, customerProducts[customerId], customerId);
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }

        map.put(Clock.getInstance().getValue(), mlog);
        return (true);		// abandona a loja com a compra efectuada
    }

    /**
     *  Verificação por parte dos clientes se a loja se encontra aberta
     *
     * @return<li> false - se a porta está fechada
     *		 <li> true -  se a porta está aberta
     */
    public synchronized boolean isDoorOpen() {
        if (storeState == true) {
            return (true);
        } else {
            return (false);
        }
    }

    /**
     * Verificação do estado de vida da dona por parte dos clientes.
     * @return <li> true, se estiver morta.
     *            <li> false, se estiver viva..
     */
    public boolean isDonaIsDead() {
        return donaIsDead;
    }

    /**
     * Função auxiliar para cálculo de um valor aleatório entre um intervalo definido por low e high.
     * @param low Valor mais baixo do intervalo (inclusive).
     * @param high Valor mais alto do intervalo (exclusive).
     * @return O valor obtido.
     */
    private int randint(int low, int high) {
        return ((int) (((high - low) * (Math.random()) + 0.5)) + low);
    }
}
